﻿using System;
using System.Data;
using System.IO;
using System.Globalization;
using System.Xml.Linq;

namespace Silverlight.DataSetConnector
{
    /// <summary>
    /// Transforms Silverlight DataSet into ADO.Net DataSet
    /// </summary>
    public sealed class Connector
    {
        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        private Connector()
        {
        }
        #endregion

        #region Methods
        /// <summary>
        /// Restores DataSet from array of bytes
        /// </summary>
        /// <param name="data">Array of bytes</param>
        public static DataSet FromArray(byte[] data)
        {
            // Validate argument
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            // Transform byte array into string
            string xmlData;
            using (MemoryStream memoryStream = new MemoryStream(data))
            {
                using (BinaryReader binaryReader = new BinaryReader(memoryStream))
                {
                    xmlData = binaryReader.ReadString();
                }
            }

            return Connector.FromXml(xmlData);
        }

        /// <summary>
        /// Restores DataSet from xml
        /// </summary>
        /// <param name="data">Xml data</param>
        /// <returns>Restored ADO.Net DataSet</returns>
        public static DataSet FromXml(string data)
        {
            // Validate argument
            if (string.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data");
            }

            // Restore DataSet from xml
            DataSet dataSet = new DataSet();
            dataSet.Locale = CultureInfo.InvariantCulture;
            using (StringReader stringReader = new StringReader(data))
            {
                dataSet.ReadXml(stringReader, XmlReadMode.ReadSchema);
            }

            dataSet.AcceptChanges();

            // Normilize DataSet
            for (int i = 0; i < dataSet.Tables.Count; i++)
            {
                // Skip copied table
                if (dataSet.Tables[i].TableName.StartsWith("CopyOf", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                // Get pair of original and copied tables
                DataTable dataTable = dataSet.Tables[i];
                DataTable copiedDataTable = dataSet.Tables["CopyOf" + dataTable.TableName];

                // Process all rows in original table
                for (int j = dataTable.Rows.Count - 1; j >= 0; j--)
                {
                    DataRow dataRow = dataTable.Rows[j];
                    switch (dataRow["RowState"].ToString())
                    {
                        case "Deleted":
                            dataRow.Delete();
                            break;
                        case "Modified":
                            // Find modified row
                            for (int k = 0; k < copiedDataTable.Rows.Count; k++)
                            {
                                DataRow copiedDataRow = copiedDataTable.Rows[k];
                                if (copiedDataRow["RowIndex"].ToString() == dataRow["RowIndex"].ToString())
                                {
                                    // Copy modified fields into original row
                                    for (int m = 0; m < dataRow.ItemArray.Length - 1; m++)
                                    {
                                        if (dataRow[m].ToString() != copiedDataRow[m].ToString())
                                        {
                                            dataRow[m] = copiedDataRow[m];
                                        }
                                    }

                                    break;
                                }
                            }

                            break;
                    }
                }

                // Process all rows in copied table
                if (copiedDataTable != null)
                {
                    for (int j = 0; j < copiedDataTable.Rows.Count; j++)
                    {
                        DataRow copiedDataRow = copiedDataTable.Rows[j];
                        if (copiedDataRow["RowState"].ToString() == "Added")
                        {
                            DataRow dataRow = dataTable.NewRow();
                            foreach (DataColumn dataColumn in dataTable.Columns)
                            {
                                dataRow[dataColumn.ColumnName] = copiedDataRow[dataColumn.ColumnName];
                            }

                            dataTable.Rows.Add(dataRow);
                        }
                    }
                }
            }

            // Remove copied tables
            for (int i = dataSet.Tables.Count - 1; i >= 0; i--)
            {
                if (dataSet.Tables[i].TableName.StartsWith("CopyOf", StringComparison.OrdinalIgnoreCase))
                {
                    dataSet.Tables.Remove(dataSet.Tables[i]);
                }
            }
            
            // Remove "RowIndex" and "RowState" columns
            for (int i = dataSet.Tables.Count - 1; i >= 0; i--)
            {
                dataSet.Tables[i].Columns.Remove(dataSet.Tables[i].Columns["RowIndex"]);
                dataSet.Tables[i].Columns.Remove(dataSet.Tables[i].Columns["RowState"]);
            }

            return dataSet;
        }

        /// <summary>
        /// Serializes ADO.Net DataSet into array of bytes
        /// </summary>
        /// <returns>Array of bytes</returns>
        public static byte[] ToArray(DataSet dataSet)
        {
            // Validate argument
            if (dataSet == null)
            {
                throw new ArgumentNullException("dataSet");
            }

            byte[] data = null;
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
                {
                    binaryWriter.Write(ToXml(dataSet));
                    data = memoryStream.ToArray();
                }
            }

            return data;
        }

        /// <summary>
        /// Serializes ADO.Net DataSet into xml / xsd data
        /// </summary>
        /// <param name="dataSet">ADO.Net DataSet</param>
        /// <returns>xml / xsd data</returns>
        public static string ToXml(DataSet dataSet)
        {
            // Validate argument
            if (dataSet == null)
            {
                throw new ArgumentNullException("dataSet");
            }

            XElement serializedDataSet = XElement.Parse(dataSet.GetXmlSchema());
            serializedDataSet.Add(XElement.Parse(dataSet.GetXml()));
            return serializedDataSet.ToString();
        }
        #endregion
    }
}
